import {
  createRenderer,
  createHydrationRenderer,
  warn,
  RootRenderFunction,
  CreateAppFunction,
  Renderer,
  HydrationRenderer,
  App,
  RootHydrateFunction,
  isRuntimeOnly,
  DeprecationTypes,
  compatUtils,
} from "@vue/runtime-core";
import { nodeOps } from "./nodeOps";
import { patchProp } from "./patchProp";
// Importing from the compiler, will be tree-shaken in prod
import {
  isFunction,
  isString,
  isHTMLTag,
  isSVGTag,
  extend,
  NOOP,
} from "@vue/shared";

declare module "@vue/reactivity" {
  export interface RefUnwrapBailTypes {
    // Note: if updating this, also update `types/refBail.d.ts`.
    runtimeDOMBailTypes: Node | Window;
  }
}

const rendererOptions = extend({ patchProp }, nodeOps);

// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer;

let enabledHydration = false;

function ensureRenderer() {
  // 单例 渲染函数 可以简单的理解这个渲染器可以帮助我们创建应用实例
  return (
    renderer ||
    // 通过 createRenderer 创建渲染函数
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  );
}

function ensureHydrationRenderer() {
  renderer = enabledHydration
    ? renderer
    : createHydrationRenderer(rendererOptions);
  enabledHydration = true;
  return renderer as HydrationRenderer;
}

// use explicit type casts here to avoid import() calls in rolled-up d.ts
export const render = ((...args) => {
  ensureRenderer().render(...args);
}) as RootRenderFunction<Element | ShadowRoot>;

export const hydrate = ((...args) => {
  ensureHydrationRenderer().hydrate(...args);
}) as RootHydrateFunction;

// 用户使用的创建应用实例的方法
export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args);

  if (__DEV__) {
    injectNativeTagCheck(app);
    injectCompilerOptionsCheck(app);
  }

  // app 就是用户通过 createApp创建的 app实例 实例上有mount方法
  const { mount } = app;
  // mount方法 接收元素 或者根 或者 字符串
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    const container = normalizeContainer(containerOrSelector);
    // 如果不能挂载就不会继续执行 也提示错误信息了
    if (!container) return;

    const component = app._component;
    if (!isFunction(component) && !component.render && !component.template) {
      // __UNSAFE__
      // Reason: potential execution of JS expressions in in-DOM template.
      // The user must make sure the in-DOM template is trusted. If it's
      // rendered by the server, the template should not contain any user data.
      // app的component如果不是函数 不是rander函数 不是template模板 只能是一串文字 用innerHtml了
      component.template = container.innerHTML;
      // 2.x compat check
      if (__COMPAT__ && __DEV__) {
        for (let i = 0; i < container.attributes.length; i++) {
          const attr = container.attributes[i];
          if (attr.name !== "v-cloak" && /^(v-|:|@)/.test(attr.name)) {
            compatUtils.warnDeprecation(
              DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
              null
            );
            break;
          }
        }
      }
    }

    // clear content before mounting
    // 挂载之前先清空一次容器的内容
    container.innerHTML = "";
    // mount方法返回的是一个 proxy 对象
    const proxy = mount(container, false, container instanceof SVGElement);
    if (container instanceof Element) {
      // 去除 v-cloak
      container.removeAttribute("v-cloak");
      // 添加 data-v-app="" 属性
      container.setAttribute("data-v-app", "");
    }
    return proxy;
  };

  return app;
}) as CreateAppFunction<Element>;

export const createSSRApp = ((...args) => {
  const app = ensureHydrationRenderer().createApp(...args);

  if (__DEV__) {
    injectNativeTagCheck(app);
    injectCompilerOptionsCheck(app);
  }

  const { mount } = app;
  app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
    const container = normalizeContainer(containerOrSelector);
    if (container) {
      return mount(container, true, container instanceof SVGElement);
    }
  };

  return app;
}) as CreateAppFunction<Element>;

function injectNativeTagCheck(app: App) {
  // Inject `isNativeTag`
  // this is used for component name validation (dev only)
  Object.defineProperty(app.config, "isNativeTag", {
    value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
    writable: false,
  });
}

// dev only
function injectCompilerOptionsCheck(app: App) {
  if (isRuntimeOnly()) {
    const isCustomElement = app.config.isCustomElement;
    Object.defineProperty(app.config, "isCustomElement", {
      get() {
        return isCustomElement;
      },
      set() {
        warn(
          `The \`isCustomElement\` config option is deprecated. Use ` +
            `\`compilerOptions.isCustomElement\` instead.`
        );
      },
    });

    const compilerOptions = app.config.compilerOptions;
    const msg =
      `The \`compilerOptions\` config option is only respected when using ` +
      `a build of Vue.js that includes the runtime compiler (aka "full build"). ` +
      `Since you are using the runtime-only build, \`compilerOptions\` ` +
      `must be passed to \`@vue/compiler-dom\` in the build setup instead.\n` +
      `- For vue-loader: pass it via vue-loader's \`compilerOptions\` loader option.\n` +
      `- For vue-cli: see https://cli.vuejs.org/guide/webpack.html#modifying-options-of-a-loader\n` +
      `- For vite: pass it via @vitejs/plugin-vue options. See https://github.com/vitejs/vite/tree/main/packages/plugin-vue#example-for-passing-options-to-vuecompiler-dom`;

    Object.defineProperty(app.config, "compilerOptions", {
      get() {
        warn(msg);
        return compilerOptions;
      },
      set() {
        warn(msg);
      },
    });
  }
}

/**
 *  规范化一个容器
 *  也就是判断这个节点是否可以进行挂载
 */
function normalizeContainer(
  container: Element | ShadowRoot | string
): Element | null {
  if (isString(container)) {
    const res = document.querySelector(container);
    // 不存在这个容器 直接返回并提示错误
    if (__DEV__ && !res) {
      // warn 提示一个警告 其实就是 console.warn(警告信息)
      warn(
        `Failed to mount app: mount target selector "${container}" returned null.`
      );
    }
    return res;
  }
  if (
    __DEV__ &&
    window.ShadowRoot &&
    container instanceof window.ShadowRoot &&
    container.mode === "closed"
  ) {
    warn(
      `mounting on a ShadowRoot with \`{mode: "closed"}\` may lead to unpredictable bugs`
    );
  }
  return container as any;
}

// Custom element support
export {
  defineCustomElement,
  defineSSRCustomElement,
  VueElement,
  VueElementConstructor,
} from "./apiCustomElement";

// SFC CSS utilities
export { useCssModule } from "./helpers/useCssModule";
export { useCssVars } from "./helpers/useCssVars";

// DOM-only components
export { Transition, TransitionProps } from "./components/Transition";
export {
  TransitionGroup,
  TransitionGroupProps,
} from "./components/TransitionGroup";

// **Internal** DOM-only runtime directive helpers
export {
  vModelText,
  vModelCheckbox,
  vModelRadio,
  vModelSelect,
  vModelDynamic,
} from "./directives/vModel";
export { withModifiers, withKeys } from "./directives/vOn";
export { vShow } from "./directives/vShow";

import { initVModelForSSR } from "./directives/vModel";
import { initVShowForSSR } from "./directives/vShow";

let ssrDirectiveInitialized = false;

/**
 * @internal
 */
export const initDirectivesForSSR = __SSR__
  ? () => {
      if (!ssrDirectiveInitialized) {
        ssrDirectiveInitialized = true;
        initVModelForSSR();
        initVShowForSSR();
      }
    }
  : NOOP;

// re-export everything from core
// h, Component, reactivity API, nextTick, flags & types
export * from "@vue/runtime-core";
